package cn.flkj.gmca.ca.gmca;

import cn.flkj.gmca.ca.gmca.gmcautil.CAUtil;
import cn.flkj.gmca.ca.gmca.gmcautil.CertAndPriKey;
import cn.flkj.gmca.ca.gmca.gmcautil.FileUtil;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.X509KeyUsage;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;

import javax.security.auth.x500.X500Principal;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.sql.SQLOutput;
import java.text.SimpleDateFormat;
import java.util.*;

import static javax.security.auth.x500.X500Principal.RFC1779;
import static javax.security.auth.x500.X500Principal.RFC2253;

/**
 * @author : 州长在手 2020/12/15 上午8:46
 */
public class GMCA {
    static {
        Security.addProvider(new BouncyCastleProvider());
//        BouncyCastleProvider bc = new BouncyCastleProvider();
//        Set<Provider.Service> services = bc.getServices();
//        for (Provider.Service s : services) {
//            if (s.toString().toUpperCase().contains("CIPHER")) System.out.println(s.toString());
//        }
    }

    /**
     * 生成国密CA根证书
     *
     * @throws Exception
     */
    public static void genGMCACert() throws Exception {
        KeyPairGenerator g = KeyPairGenerator.getInstance("EC", "BC");
        g.initialize(new ECNamedCurveGenParameterSpec("sm2p256v1"));
        KeyPair p = g.generateKeyPair();
        PrivateKey privKey = p.getPrivate();
        PublicKey pubKey = p.getPublic();
        System.out.println("CA PrivateKey:" + Base64.toBase64String(privKey.getEncoded()));
        X500Principal iss = new X500Principal("CN=test GM ROOT CA,OU=test,C=CN,S=Guangdong,O=test");
        ContentSigner sigGen = new JcaContentSignerBuilder("SM3withSM2").setProvider("BC").build(privKey);
        X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(
                iss,
                BigInteger.valueOf(1),
                new Date(System.currentTimeMillis() - 50000),
                new Date(System.currentTimeMillis() + 50000),
                iss,
                pubKey).addExtension(new ASN1ObjectIdentifier("2.5.29.15"), true,
                new X509KeyUsage(0xfe))
                .addExtension(new ASN1ObjectIdentifier("2.5.29.37"), true,
                        new DERSequence(KeyPurposeId.anyExtendedKeyUsage))
                .addExtension(new ASN1ObjectIdentifier("2.5.29.17"), true,
                        new GeneralNames(new GeneralName[]
                                {
                                        new GeneralName(GeneralName.rfc822Name, "gmca@test.cn"),
                                        new GeneralName(GeneralName.dNSName, "ca.test.cn")
                                }));
        X509Certificate cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certGen.build(sigGen));
        cert.checkValidity(new Date());
        cert.verify(pubKey);
        ByteArrayInputStream bIn = new ByteArrayInputStream(cert.getEncoded());
        CertificateFactory fact = CertificateFactory.getInstance("X.509", "BC");
        cert = (X509Certificate) fact.generateCertificate(bIn);
        String base64cert = Base64.toBase64String(cert.getEncoded());
        FileUtil.saveCerFile("CARootCert.cer", base64cert);
        FileUtil.savePriFile("CAPrikey", Base64.toBase64String(privKey.getEncoded()));
        //saveFile("CAPrikey",  Base64.toBase64String(privKey.getEncoded()).getBytes());
        //saveFile("CARootCert.cer", Base64.toBase64String(cert.getEncoded()).getBytes());
        System.out.println("=============测试生成国密CA根证书=============");
    }

    /**
     * 国密CA根证书签发国密证书
     *
     * @param cn         名字
     * @param rfc822Name 邮箱
     * @param dNSName    dns
     * @return 返回证书和私钥Base64的组合类
     * @throws Exception 随意
     */
    public static CertAndPriKey genCertWithCaSign(String cn, String rfc822Name, String dNSName) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance("EC");
        // 在这里读取 CA 根证书！！！
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(CAUtil.getPriKey());
        PrivateKey caPrivateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        CertificateFactory certificateFactory = CertificateFactory.getInstance("X509", "BC");
        byte[] cer = CAUtil.getCert();
        InputStream inputStream = new ByteArrayInputStream(cer);
        Certificate caRootCert = certificateFactory.generateCertificate(inputStream);
        KeyPairGenerator g = KeyPairGenerator.getInstance("EC", "BC");
        g.initialize(new ECNamedCurveGenParameterSpec("sm2p256v1"));
        // 创建下级证书的密钥对
        KeyPair p = g.generateKeyPair();
        PrivateKey privKey = p.getPrivate();
        PublicKey pubKey = p.getPublic();
        ContentSigner sigGen = new JcaContentSignerBuilder("SM3withSM2").setProvider("BC").build(caPrivateKey);
        X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(
                (X509Certificate) caRootCert,
                BigInteger.valueOf(new Random().nextInt()),
                // 颁发证书的时间这里是1年
                new Date(System.currentTimeMillis()),
                new Date(System.currentTimeMillis() + 31536000000L),
                new X500Principal("CN=" + cn),
                pubKey)
                .addExtension(new ASN1ObjectIdentifier("2.5.29.15"), true,
                new X509KeyUsage(X509KeyUsage.digitalSignature | X509KeyUsage.nonRepudiation))
                .addExtension(new ASN1ObjectIdentifier("2.5.29.37"), true,
                        new DERSequence(KeyPurposeId.anyExtendedKeyUsage))
                .addExtension(new ASN1ObjectIdentifier("2.5.29.17"), true,
                        new GeneralNames(new GeneralName[]
                                {
                                        new GeneralName(GeneralName.rfc822Name, rfc822Name),
                                        new GeneralName(GeneralName.dNSName, dNSName)
                                }));
        X509Certificate cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certGen.build(sigGen));
        cert.checkValidity(new Date());
        cert.verify(caRootCert.getPublicKey());
        ByteArrayInputStream bIn = new ByteArrayInputStream(cert.getEncoded());
        CertificateFactory fact = CertificateFactory.getInstance("X.509", "BC");
        cert = (X509Certificate) fact.generateCertificate(bIn);
        CertAndPriKey certAndPriKey = new CertAndPriKey(privKey.getEncoded(), cert.getEncoded());
        FileUtil.saveCerFile("custCert.cer", Base64.toBase64String(cert.getEncoded()));
        FileUtil.savePriFile("custPrivateKey", Base64.toBase64String(privKey.getEncoded()));
        return certAndPriKey;
    }

    /**
     * 数字签名
     *
     * @param certAndPriKey 存储对象
     * @param signText      需要签字的消息
     * @return 数字签名
     * @throws Exception 可能有异常个鬼
     */
    public static byte[] digitalSign(CertAndPriKey certAndPriKey, String signText) throws Exception {
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decode(certAndPriKey.getBase64PriKey()));
        KeyFactory keyFactory = KeyFactory.getInstance("EC");
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        Signature signature = Signature.getInstance("SM3withSM2", "BC");
        signature.initSign(privateKey);
        signature.update(signText.getBytes("UTF-8"));
        return signature.sign();
    }

    /**
     * 验 签
     *
     * @param certAndPriKey    存储对象
     * @param digitalSignature 数字签名
     * @param signText         需要验证的消息
     * @return 检验结果
     * @throws Exception 可能有异常个鬼
     */
    public static boolean signedInspection(CertAndPriKey certAndPriKey, byte[] digitalSignature, String signText) throws Exception {
        CertificateFactory certificateFactory = CertificateFactory.getInstance("X509", "BC");
        // 获取证书
        byte[] custCert = Base64.decode(certAndPriKey.getBase64Cert());
        // 把byte数组变成InputStream
        InputStream inputStream = new ByteArrayInputStream(custCert);
        Certificate certificate = certificateFactory.generateCertificate(inputStream);
        Signature signature = Signature.getInstance("SM3withSM2", "BC");
        signature.initVerify(certificate.getPublicKey());
        signature.update(signText.getBytes("UTF-8"));
        return signature.verify(digitalSignature);
    }

    /**
     * 使用SM2加密数据
     * @param certAndPriKey
     * @param data
     * @return
     * @throws Exception
     */
    public static byte[] sM2Ec(CertAndPriKey certAndPriKey, String data) throws Exception {
        CertificateFactory certificateFactory = CertificateFactory.getInstance("X509", "BC");
        // 获取证书
        byte[] custCert = Base64.decode(certAndPriKey.getBase64Cert());
        // 把byte数组变成InputStream
        InputStream inputStream = new ByteArrayInputStream(custCert);
        Certificate certificate = certificateFactory.generateCertificate(inputStream);
        PublicKey publicKey = certificate.getPublicKey();
        //获取加密参数
        BCECPublicKey localECPublicKey = (BCECPublicKey) publicKey;
        ECParameterSpec localECParameterSpec = localECPublicKey.getParameters();
        ECDomainParameters localECDomainParameters = new ECDomainParameters(
                localECParameterSpec.getCurve(), localECParameterSpec.getG(),
                localECParameterSpec.getN());
        ECPublicKeyParameters localECPublicKeyParameters = new ECPublicKeyParameters(localECPublicKey.getQ(),
                localECDomainParameters);
        //待加密数据
        byte[] ebs = data.getBytes("UTF-8");
        //初始化加密引擎
        SM2Engine sm2EncEngine = new SM2Engine();
        sm2EncEngine.init(true, new ParametersWithRandom(localECPublicKeyParameters));
        //加密
        return sm2EncEngine.processBlock(ebs, 0, ebs.length);
    }

    /**
     * 使用SM2解密数据
     * @param certAndPriKey
     * @param ciphertext
     * @return
     * @throws Exception
     */
    public static String sm2Dc(CertAndPriKey certAndPriKey, byte[] ciphertext) throws Exception {
        // 读私钥
        byte[] custPrivateKey = Base64.decode(certAndPriKey.getBase64PriKey());
        //获取私钥
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(custPrivateKey);
        KeyFactory keyFactory = KeyFactory.getInstance("EC");
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        //获取解密参数
        BCECPrivateKey sm2PriK = (BCECPrivateKey) privateKey;
        ECParameterSpec ecParameterSpec = sm2PriK.getParameters();
        ECDomainParameters ecDomainParameters = new ECDomainParameters(
                ecParameterSpec.getCurve(), ecParameterSpec.getG(),
                ecParameterSpec.getN());
        ECPrivateKeyParameters localECPrivateKeyParameters = new ECPrivateKeyParameters(
                sm2PriK.getD(), ecDomainParameters);
        SM2Engine sm2DcEngine = new SM2Engine();
        sm2DcEngine.init(false, localECPrivateKeyParameters);
        byte[] b = sm2DcEngine.processBlock(ciphertext, 0, ciphertext.length);
        return new String(b);
    }


    /**
     * 验证证书是否是根证书颁发
     * @param subCert
     * @return
     * @throws Exception
     */
    public static String rootCertCheckSubCert(String subCert) throws Exception {

        CertificateFactory certificateFactory = CertificateFactory.getInstance("X509", "BC");
        // 获取cust证书
        byte[] custCert = Base64.decode(subCert);
        // 把byte数组变成InputStream
        InputStream custInputStream = new ByteArrayInputStream(custCert);
        X509Certificate custCertificate = (X509Certificate) certificateFactory.generateCertificate(custInputStream);
        // 获取Root证书
        InputStream rootInputStream = new ByteArrayInputStream(CAUtil.getCert());
        X509Certificate rootCertificate = (X509Certificate) certificateFactory.generateCertificate(rootInputStream);
        // 检验是否是根证书颁发的证书的结果
        boolean verify = false;
        // 检验证书是否是有效的，无论是根证书还是其他根证书颁发的证书，都可以验证有效性
        boolean checkValidity = false;
        try {
            custCertificate.verify(rootCertificate.getPublicKey());
            verify = true;
        }catch (Exception ignored){}
        try {
            custCertificate.checkValidity();
            checkValidity = true;
        }catch (Exception ignored){};
        String result = "{";
        if (verify&&checkValidity){
            // 获取颁发者X500主体
            X500Principal rootX500Principal = custCertificate.getIssuerX500Principal();
            String  Issuer =  CAUtil.parseIssuerX500Principal(rootX500Principal).get("CN");
            String name = custCertificate.getSubjectX500Principal().toString();
            String sub = name.substring(name.indexOf("CN=")+3);
            result = result + "\"verify\":\""+verify+"\",";
            result = result + "\"checkValidity\":\""+checkValidity+"\",";
            result = result + "\"Issuer\":\""+Issuer+"\",";
            result = result + "\"sub\":\""+sub+"\",";
            result = result + "\"getNotBefore\":\""+custCertificate.getNotBefore()+"\",";
            result = result + "\"getNotAfter\":\""+custCertificate.getNotAfter()+"\"}";
        }else {
            result = result + "\"verify\":\""+verify+"\",";
            result = result + "\"checkValidity\":\""+checkValidity+"\"}";
        }

        return result;
    }

    // 这里配合区块链使用就是获取序列号
    // 把序列号存入区块链并做标记

    /**
     * 获取序列号
     * @param subCert
     * @return
     * @throws Exception
     */
    public static String serialNumber(String subCert)throws Exception {
        CertificateFactory certificateFactory = CertificateFactory.getInstance("X509", "BC");
        // 获取cust证书
        byte[] custCert = Base64.decode(subCert);
        // 把byte数组变成InputStream
        InputStream custInputStream = new ByteArrayInputStream(custCert);
        X509Certificate custCertificate = (X509Certificate) certificateFactory.generateCertificate(custInputStream);
        return  custCertificate.getSerialNumber().toString();
    }

}
